package com.le123.ysdq.sopacker

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree

import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

class SoPackerPlugin implements Plugin<Project> {


    def tag = "==========SoPackerPlugin=========="
    def logger

    @Override
    def void apply(Project project) {
        logger = project.logger
        def soPacker = project.container(Rule)
        soPacker.all {
            // do nothing
        }
        project.extensions.soPacker = soPacker
        project.afterEvaluate {
            configPlugin(project, soPacker)
        }
    }

    void configPlugin(Project project, def soPacker) {
        project.android.applicationVariants.all { variant ->
            def excludeList = []
            def includeList = []
            Map<String, String> excludeMap = new HashMap<>()

            // find all flavors and build type of this variont
            def flavors = (variant.productFlavors.name).collect()
            flavors << variant.buildType.name

            // detect all excluding rules for this variant
            soPacker.matching { rule ->
                flavors.any { flavorName ->
                    flavorName.matches(rule.name)
                }
            }.all { rule ->
                excludeList.addAll(rule.excludeList)
                includeList.addAll(rule.includeList)
                excludeMap.putAll(rule.excludeMap)
            }

            // Find and remove excluded so files before package application
            variant.outputs.each { output ->
                output.packageApplication.doFirst { pkgApp ->
                    boolean dealWithIt = excludeList.size > 0 || includeList.size > 0
                    if (dealWithIt) {
                        pkgApp.jniFolders.each { dir ->
                            // find all so files needed by this output
                            FileTree reserve = project.fileTree(dir) { fileTree ->
                                includeList?.each {
                                    fileTree.include it
                                }

                                excludeList?.each {
                                    fileTree.exclude it
                                }
                            }

                            // remove all excluded so files
                            FileCollection remove = project.fileTree(dir).minus(reserve)
                            logger.quiet "* Removing for ${output.name}: ${remove.getAsPath()}"
                            remove.each {
                                it.delete()
                            }
                        }
                    }

                    logger.quiet "${tag} excludeMap size:${excludeMap.size()}"
                    boolean dealWithMapIt = excludeMap.size() > 0
                    if (dealWithMapIt) {
                        pkgApp.jniFolders.each { dir ->
                            // find all so files needed by this output
                            FileTree reserve = project.fileTree(dir) { fileTree ->
                                excludeMap?.each {
                                    it.value?.each { itDetail ->
                                        logger.quiet "${tag} exclude Item key ${it.key}, item value ${itDetail}"
                                        fileTree.exclude itDetail
                                    }
                                }
                            }

                            packerSoByKey(project, variant, dir, excludeMap)
                            // remove all excluded so files
                            FileCollection remove = project.fileTree(dir).minus(reserve)
                            logger.quiet "* Removing for ${output.name}: ${remove.getAsPath()}"
                            remove.each {
                                it.delete()
                            }
                        }
                    }
                }
            }
        }
    }

    private void packerSoByKey(Project project, def variant, def dir, def excludeMap) {
        def versionName = variant.generateBuildConfig.versionName
        def versionCode = variant.generateBuildConfig.versionCode
        //clear build zip dir
        def soZipDirName = "${project.rootDir.absolutePath}/build/so-zips"
        File soZipDir = new File(soZipDirName)
        if (soZipDir.exists()) soZipDir.deleteDir()
        //packer so by key
        excludeMap?.each {
            def key = it.key
            def soFileNames = it.value
            //add so to zip entry
            ZipOutputStream output = null
            try {
                logger.quiet "${tag} soFileNames size:${soFileNames.size}"
                soFileNames?.each { soFileName ->
                    File soFile = new File("${dir.absolutePath}/${soFileName}")
                    //mk build/so-zips/arm* dir
                    def archName = soFile.parentFile.name
                    def soZipArchDirName = "${soZipDirName}/${versionName}/${archName}"
                    File rootBuildZipArchDir = new File(soZipArchDirName)
                    if (!rootBuildZipArchDir.exists()) rootBuildZipArchDir.mkdirs()
                    //set zip file name
                    if (output == null) {
                        def zipFileName = "${soZipArchDirName}/${key}-${archName}-v${versionName}-${versionCode}.zip"
                        output = new ZipOutputStream(new FileOutputStream(zipFileName))
                    }
                    //write zip
                    logger.quiet "${tag} soFile:${soFile.absolutePath}"
                    output.putNextEntry(new ZipEntry(soFile.name))
                    // Stream the document data to the ZIP
                    soFile.withInputStream { input ->
                        output << input
                    }
                    output.closeEntry();
                }
            } catch (Exception e) {
                logger.quiet "${tag} Zip so entry error:${e.message}"
            } finally {
                try {
                    output.close()
                } catch (Exception e) {
                    logger.quiet "${tag} Zip so entry error:${e.message}"
                }
            }
        }
    }

}
